package ace.module.oauth2.server.core.impl.authentication.password;

import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/**
 * @author caspar
 * @date 2023/2/8 13:40 为spring authorization server 添加 oauth2 的password 模式
 */
@Component
public class Oauth2PasswordAuthenticationConverter implements AuthenticationConverter {
  private static final String USERNAME_PARAM_NAME = "username";
  private static final String PASSWORD_PARAM_NAME = "password";

  @Override
  public OAuth2AuthorizationGrantAuthenticationToken convert(HttpServletRequest request) {
    // grant_type (REQUIRED)
    String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
    if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
      return null;
    }

    OAuth2ClientAuthenticationToken clientPrincipal =
        (OAuth2ClientAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();

    String username = this.getUserNameFromRequest(request);
    String password = this.getPasswordFromRequest(request);
    MultiValueMap<String, String> parameters = this.getParameters(request);

    Map<String, Object> additionalParameters = new HashMap<>();
    parameters.forEach(
        (key, value) -> {
          if (!key.equals(OAuth2ParameterNames.GRANT_TYPE)
              && !key.equals(USERNAME_PARAM_NAME)
              && !key.equals(PASSWORD_PARAM_NAME)) {
            additionalParameters.put(key, value.get(0));
          }
        });
    return Oauth2PasswordAuthenticationToken.builder()
        .username(username)
        .password(password)
        .clientPrincipal(clientPrincipal)
        .additionalParameters(additionalParameters)
        .build();
  }

  private String getPasswordFromRequest(HttpServletRequest request) {
    String password = request.getParameter("password");
    Assert.hasText(password, "password is not be empty");
    return password;
  }

  private String getUserNameFromRequest(HttpServletRequest request) {
    String username = request.getParameter("username");
    Assert.hasText(username, "username is not be empty");
    return username;
  }

  private MultiValueMap<String, String> getParameters(HttpServletRequest request) {
    Map<String, String[]> parameterMap = request.getParameterMap();
    MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
    parameterMap.forEach(
        (key, values) -> {
          if (values.length > 0) {
            for (String value : values) {
              parameters.add(key, value);
            }
          }
        });
    return parameters;
  }
}
